//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Extensibility;

namespace OpenLS.Spreadsheet
{
    ///<summary>
    /// Represents a range that span one or many full columns.
    ///</summary>
    public class FullColumnRange : FullRange
    {
        /// <summary>
        /// Initializes a new instance of FullColumnRange.
        /// </summary>
        /// <param name="column"></param>
        public FullColumnRange(Column column)
            : this(column, column)
        {
        }
        internal override IEnumerable<Row> GetExistingRows()
        {
            return Worksheet.Rows;
        }

        internal override string LabelForSelection
        {
            get
            {
                return FirstColumn.Label + "1" + ":" + LastColumn.Label + Worksheet.Rows[Worksheet.MaxRowIndex].Label;
            }
        }
        internal override bool Contains(Range range)
        {
            if (range is CellsRange)
            {
                var cr = (CellsRange)range;
                return Contains(cr.TopLeftCell) && Contains(cr.BottomRight);
            }
            if (range is FullColumnRange)
            {
                var rr = (FullColumnRange)range;
                return ContainsColumn(rr.FirstColumn) && ContainsColumn(rr.LastColumn);
            }
            return ContainsCompositeRange(range);
        }
        internal override SimpleRange ProjectedSimple(Worksheet worksheet, CellRelativeAddress diff)
        {
            return new FullColumnRange(worksheet.Columns[FirstColumn.Index + diff.Column], worksheet.Columns[LastColumn.Index + diff.Column]);
        }
        internal override IEnumerable<PartialRowOrColumnRange> GetPartialRowOrColumnRanges(RowOrColumn rowColumn)
        {

            List<Cell> cells = new List<Cell>(GetNonEmptyCells());
            cells.Sort(new Comparison<Cell>(delegate(Cell c1, Cell c2)
            {
                int i = c1.Row.Index.CompareTo(c2.Row.Index);
                if (i != 0)
                    return i;
                return c2.Column.Index.CompareTo(c2.Column.Index);
            }));
            Cell previousCell = null;

            foreach (Cell cell in cells)
            {
                if (previousCell == null || previousCell.Row != cell.Row)
                {
                    yield return new PartialRowOrColumnRange(Worksheet, new CellAddress(cell.Row.Index, TopLeftCell.Column.Index),
                        ColumnSpan, RowOrColumn.Row); //\\
                }
                previousCell = cell;
            }
        }

        internal override RangeReference ToReference()
        {
            return new FullColumnRangeReference(new AnchoredColumnReference(FirstColumn, true),
                new AnchoredColumnReference(LastColumn, true));
        }
        internal override Range GetColumnRange(int index, RangeType type)
        {
            return new FullColumnRange(FirstColumn.Worksheet.Columns[FirstColumn.Index + index]);
        }

        internal override void SetBorderNoUndo(Border border, BorderType borderType)
        {
            if (borderType == BorderType.All)
            {
                foreach (Cell cell in this.GetNonEmptyCells())
                    cell.Borders = null;
                foreach (Column column in this.GetColumns())
                {
                    column.SetBorder(borders => borders.Left = border);
                    column.SetBorder(borders => borders.Right = border);
                    column.SetBorder(borders => borders.Top = border);
                    column.SetBorder(borders => borders.Bottom = border);
                }
                return;
            }
            if ((borderType & BorderType.LeftEdge) != 0)
            {
                this.FirstColumn.SetBorder(borders => borders.Left = border);
            }
            if ((borderType & BorderType.RightEdge) != 0)
            {
                this.LastColumn.SetBorder(borders => borders.Right = border);
            }
            if ((borderType & BorderType.TopEdge) != 0)
            {
                foreach (Cell cell in Enumerable.Select(this.GetColumns(), c => Worksheet[0, c.Index]))
                {
                    cell.SetBorder(border, BorderType.TopEdge);
                }
            }
            if ((borderType & BorderType.BottomEdge) != 0)
            {
                foreach (Cell cell in Enumerable.Select(this.GetColumns(), c => Worksheet[Worksheet.MaxRowIndex, c.Index]))
                {
                    cell.SetBorder(border, BorderType.BottomEdge);
                }
            }
            if ((borderType & BorderType.InsideHorizontal) != 0)
            {
                foreach (Column column in GetColumns())
                {
                    column.SetBorder(borders => borders.Top = border);
                    column.SetBorder(borders => borders.Bottom = border);
                }
                foreach (var cell in Enumerable.Where( GetNonEmptyCells(), c => c.Borders != null || c.Row.Borders != null))
                {
                    if (cell.Borders == null)
                        cell.Borders = new Borders();
                    if (cell.Row.Index != 0 && (cell.Borders.Top != null && cell.Row.Borders != null))
                    {
                        cell.SetBorder(border, BorderType.TopEdge);
                    }
                    if (cell.Row.Index != Worksheet.MaxRowIndex && (cell.Borders.Bottom != null || cell.Row.Borders != null))
                    {
                        cell.SetBorder(border, BorderType.BottomEdge);
                    }
                }
            }
            if ((borderType & BorderType.InsideVertical) != 0)
            {
                foreach (Column column in GetColumns())
                {
                    if (column != this.FirstColumn)
                      column.SetBorder(borders => borders.Left = border);
                    if (column != this.LastColumn)
                        column.SetBorder(borders => borders.Right = border);
                }
                foreach (var cell in Enumerable.Where(GetNonEmptyCells(), c => c.Borders != null  || c.Row.Borders != null))
                {
                    if (cell.Borders == null)
                        cell.Borders = new Borders();
                    if (cell.Column != FirstColumn && (cell.Borders.Left != null && cell.Row.Borders != null))
                    {
                        cell.SetBorder(border, BorderType.LeftEdge);
                    }
                    if (cell.Column != LastColumn && (cell.Borders.Right != null && cell.Row.Borders != null))
                    {
                        cell.SetBorder(border, BorderType.RightEdge);
                    }
                }
            }

        }

        internal override Range GetRowRange(int index, RangeType type)
        {
            return new MultiCellsRange(Worksheet, new RangeAddress(new CellAddress(index, FirstColumn.Index), new CellAddress(index, LastColumn.Index)));
        }

        ///<summary>
        /// Get the cell in the top left corner of the range
        ///</summary>
        public override Cell TopLeftCell
        {
            get { return Worksheet[0, FirstColumn.Index]; }
        }
        public FullColumnRange(Column column1, Column column2) : base(column1.Worksheet)
        {
            Contract.Requires(column1 != null);
            Contract.Requires(column2 != null);
            if (column1.Index < column2.Index)
            {
                _firstColumn = column1;
                _lastColumn = column2;
            }
            else
            {
                _firstColumn = column2;
                _lastColumn = column1;
            }
        }


        private Column _firstColumn;
        private Column _lastColumn;
        internal Column FirstColumn { get { return _firstColumn; } private set {this.Worksheet.Workbook.AddUndoAction(
            FirstColumn, f => FirstColumn = f); _firstColumn = value; } }
        internal Column LastColumn { get { return _lastColumn; } private set {this.Worksheet.Workbook.AddUndoAction(LastColumn, l => LastColumn = value); _lastColumn = value; } }
        internal  Column anchor{get{ return _firstColumn;}}
        public override int ColumnSpan
        {
            get
            {
                return LastColumn.Index - FirstColumn.Index + 1;
            }
        }


        internal override IEnumerable<Dimension> GetDimensions()
        {
            for (int i = FirstColumn.Index; i <= LastColumn.Index; i++)
                yield return Worksheet.Columns[i];
        }
        internal bool Intersects(FullColumnRange r)
        {
            bool b1 = FirstColumn.Index <= r.FirstColumn.Index && r.FirstColumn.Index <= LastColumn.Index;
            bool b2 = r.FirstColumn.Index <= FirstColumn.Index && FirstColumn.Index <= r.LastColumn.Index;
            return b1 || b2;
        }

        internal override void OnDeletingRange(RangeChangeEventArgs e)
        {
            Contract.Assert(e != null);
            Contract.Assert(e.Type == RangeChangeType.Deletion);
            if (e.Address.IsFullColumn && e.Hint == CellMoveOrientationHint.Horizontal)
            {
                if (e.Address.Contains(new CellAddress(0, FirstColumn.Index)))
                {
                    FirstColumn = Worksheet.Columns[e.Address.BottomRight.Column + 1];

                }
                if (e.Address.Contains(new CellAddress(0, LastColumn.Index)))
                {
                    LastColumn = Worksheet.Columns[Math.Max(e.Address.TopLeft.Column - 1, 0)];
                }
            }
        }

        internal override Dimension FirstDimension
        {
            get { return FirstColumn; }
        }

        internal override Dimension LastDimension
        {
            get { return LastColumn; }
        }

        internal override IEnumerable<Border> GetBorders(BorderType borderType)
        {
            switch (borderType)
            {
                case BorderType.LeftEdge:
                    {
                        var cellBorders = Enumerable.Select(FirstColumn.GetNonEmptyCells(),
                                                            c => c.GetActualBorder(borderType));
                        var rowBorders = Enumerable.Select(this.GetNonEmptyRows(),
                                                           r =>
                                                           r.Borders != null ? r.Borders.GetBorder(borderType) : null);
                        rowBorders = Enumerable.Distinct(Enumerable.Where(rowBorders, b => b != null));
                        return Enumerable.Concat(cellBorders, rowBorders,
                                                 new Border[]
                                                     {
                                                         FirstColumn.Borders != null
                                                             ? FirstColumn.Borders.GetBorder(borderType)
                                                             : null
                                                     });
                    }
                case BorderType.RightEdge:
                    {
                        var cellBorders = Enumerable.Select(LastColumn.GetNonEmptyCells(),
                                                               c => c.GetActualBorder(borderType));
                        var rowBorders = Enumerable.Select(this.GetNonEmptyRows(),
                                                           r =>
                                                           r.Borders != null ? r.Borders.GetBorder(borderType) : null);
                        rowBorders = Enumerable.Distinct(Enumerable.Where(rowBorders, b => b != null));
                        return Enumerable.Concat(cellBorders, rowBorders,
                                                 new Border[]
                                                     {
                                                         LastColumn.Borders != null
                                                             ? LastColumn.Borders.GetBorder(borderType)
                                                             : null
                                                     });
                        
                    }
                case BorderType.TopEdge:
                    {
                        var cells = Enumerable.Select(this.GetColumns(), column => column.Worksheet[0, column.Index]);
                        return Enumerable.Select(cells, cell => cell.GetActualBorder(borderType));
                    }
                case BorderType.BottomEdge:
                    {
                        var cells = Enumerable.Select(this.GetColumns(), column => column.Worksheet[Worksheet.MaxRowIndex, column.Index]);
                        return Enumerable.Select(cells, cell => cell.GetActualBorder(borderType));
                    }
                case BorderType.DiagonalUp:
                case BorderType.DiagonalDown
                    :
                    {
                        var cellBorders = Enumerable.Select(this.GetNonEmptyCells(),
                                                              c => c.GetActualBorder(borderType));
                        var rowBorders = Enumerable.Select(this.GetNonEmptyRows(),
                                                           r =>
                                                           r.Borders != null ? r.Borders.GetBorder(borderType) : null);
                        rowBorders = Enumerable.Distinct(Enumerable.Where(rowBorders, b => b != null));
                        var columnBorders = Enumerable.Select(this.GetNonEmptyColumns(),
                                                           r =>
                                                           r.Borders != null ? r.Borders.GetBorder(borderType) : null);
                        columnBorders = Enumerable.Distinct(Enumerable.Where(columnBorders, b => b != null));
                        return Enumerable.Concat(cellBorders, rowBorders, columnBorders);
                        
                       
                    }
                case BorderType.InsideHorizontal:
                case BorderType.InsideVertical:
                    return new Border[0];
                default:
                    throw new NotSupportedException();
            }
        }

        internal override Cell FirstCell
        {
            get
            {
                Contract.Ensures(Contract.Result<Cell>() != null);

                Cell result = FirstColumn[0];
                return result;
            }
        }
        public override int Column
        {
            get
            {
                return FirstColumn.Index;
            }
        }
        public override int Row
        {
            get
            {
                return 0;
            }
        }


        internal override bool Contains(CellAddress address)
        {
            if (address.Column < FirstDimension.Index)
                return false;
            if (address.Column > LastDimension.Index)
                return false;
            return true;
        }
        internal bool Contains(Column column)
        {
            if (column.Worksheet != FirstColumn.Worksheet)
                return false;
            return column.Index >= FirstColumn.Index && column.Index <= LastColumn.Index;
        }

        public override int RowSpan
        {
            get { return Spreadsheet.Row.MaxSpan; }
        }

        public override bool IsFullRow
        {
            get { return false; }
        }

        public override bool IsFullColumn
        {
            get { return true; }
        }

        internal override RangeAddress Address
        {
            get
            {
                return new RangeAddress(new CellAddress(0, FirstColumn.Index), new CellAddress(Spreadsheet.Row.MaxIndex, LastColumn.Index));
            }
        }

        public override Cell this[int row, int column]
        {
            get { return Worksheet[row, FirstColumn.Index + column]; }
        }

        internal override Cell GetNextCell(Cell cell, CellNavigationDirection direction)
        {
            switch (direction)
            {
                case CellNavigationDirection.Forward:

                    if (cell.Column == LastColumn)
                    {
                        if (cell.Row.Index == RowSpan)
                            return Worksheet[0, FirstColumn.Index];
                        return Worksheet[cell.Row.Index + 1, FirstColumn.Index];
                    }
                    return Worksheet[cell.Row.Index, cell.Column.Index + 1];
                case CellNavigationDirection.Backward:
                    if (cell.Column == FirstColumn)
                    {
                        if (cell.Row.Index == 0)
                            return Worksheet[RowSpan, LastColumn.Index];
                        return Worksheet[cell.Row.Index - 1, LastColumn.Index];
                    }
                    return Worksheet[cell.Row.Index, cell.Column.Index - 1];
                default:
                    throw new NotSupportedException();
            }
        }


        public override IEnumerable<Cell> GetAllCells()
        {
            for (int column = FirstColumn.Index; column <= LastColumn.Index; column++)
            {
                for (int row = 0; row <= Spreadsheet.Row.MaxIndex; row++)
                {
                    yield return Worksheet[row, column];
                }
            }
        }

        internal override void SetCellValueNoUndo(CellProperty p, object value)
        {
            SetCellValueNoUndoForSelf(p, value);
            foreach (Row r in Worksheet.Rows)
                if (!Equals(r.GetCellValue(p), value))
                {
                    foreach (Column column in GetColumns())
                    {
                        var cell = Worksheet.GetNonVolatileCell(r.Index, column.Index);
                        if (cell != null)
                            cell.SetCellValue(p, value);
                    }
                }

        }
        public override IEnumerable<Column> GetColumns()
        {
            for (int i = FirstColumn.Index; i <= LastColumn.Index; i++)
                yield return Worksheet.Columns[i];
        }


        internal override Range Offseted(CellRelativeAddress address)
        {
            return new FullColumnRange(FirstColumn + address.Column, LastColumn + address.Column);
        }

        public override IEnumerable<Row> GetRows()
        {
            foreach (Row row in Worksheet.Rows)
                yield return row;
        }

    }

}
